#!/usr/bin/ruby1.8
#
# Copyright (c) 2007 Fumitoshi Ukai <ukai@debian.or.jp>
# This program is covered by the GNU General Public License 2
#
# depends: ruby1.8, libxmpp4r-ruby1.8
#
# Configuration file: ~/.monogikirc
# The file should contains the following variables:
## configuration file for monogiki
# MYJID = 'yourname@gmail.com/Monogiki'
# MYJIDPASSWD = 'your-gmail-password'
# TARGETJIDLIST = ['twitter@twitter.com', 'ja2en@bot.talk.google.com']
# HTTPADDR = '0.0.0.0'
# HTTPPORT = 8088
# HTTPUSER = 'username'
# HTTPPASSWD = 'password'
# 
$KCODE = "u"
require 'kconv'
require 'rexml/document'
require 'time'
require 'webrick'
require 'webrick/httpauth'
require 'webrick/httputils'
require 'xmpp4r'

module Monogiki
  VERSION = '0.0.2'
end

class XmppProxy
  attr_reader :messages
  MAX_LINES = 50
  LOGFILE =  "./monogiki.log"
 
  def initialize(config)
    @logger = config[:Logger]
    # Jabber::debug = true
    @pass = config[:MyPassword]
    @targetjidlist = config[:TargetJIDList].collect do |jid|
      @logger.info("target #{jid}")
      Jabber::JID.new(jid)
    end
    @client = Jabber::Client.new(Jabber::JID.new(config[:MyJID]))
    @messages = {}
    if File.readable? LOGFILE
      @logger.info("Loading logfile: #{LOGFILE}")
      File.readlines(LOGFILE).each do |line|
        jid, msg = line.chomp.split(' ', 2)
        if check_target(jid)
          putmsg(jid, msg)
        end
      end
      @logger.info("done")
    end
    @closed = false

    @client.connect
    @client.auth(@pass)
    @client.send(Jabber::Presence::new)
    @client.add_message_callback do |message|
      if message.type == :error
        logger.debug("error message received.")
        return
      end
      from = message.from
      if from.nil?
        logger.debug("from not found.")
        return
      end
      if check_target(from)
        addmsg(from, "#{from.node} #{message.body}")
      end
    end
    @client.on_exception do |ex, stream, event|
      @logger.error("Exception #{ex} #{event}")
      if @closed
        @logger.info("closed. bye")
      end
      @logger.info("reconnect xmpp session.")
      @client.connect
      @client.auth(@pass)
      @client.send(Jabber::Presence::new)
    end
    @keepalive = Thread.new do
      while not @closed
        if @client.is_connected?
          @logger.debug("send presence")
          @client.send(Jabber::Presence::new)
        end
        sleep 30
      end
    end
  end

  def list_targets()
    return @targetjidlist
  end

  def check_target(jid)
    return @targetjidlist.include?(Jabber::JID.new(jid))
  end

  def get_messages(jid)
    return @messages[Jabber::JID.new(jid)] || []
  end
 
  def send(jid, msg)
    @logger.debug("send #{jid} #{msg}")
    targetjid = Jabber::JID.new(jid)
    @client.send(Jabber::Message.new(targetjid, msg).set_type(:chat))
    addmsg(targetjid, "me #{msg}")
  end

  def addmsg(jid, msg)
    if msg.nil?
      return
    end
    line = "#{Time.now.strftime('%H:%M')} #{msg}"
    putmsg(jid, line)
    open(LOGFILE, "a") do |fp| fp.puts "#{jid} #{line}\n" end
  end

  def putmsg(jid, msg)
    jid = Jabber::JID.new(jid).strip
    @messages[jid] ||= []
    @messages[jid].unshift(msg)
    if @messages[jid].length > MAX_LINES
      @messages[jid].pop
    end
  end

  def close()
    @client.close
    @closed = true
    @keepalive.join
  end
end # XmppProxy

class XmppServlet < WEBrick::HTTPServlet::AbstractServlet
  AUTHREALM = "Monogiki"

  def initialize(server, *options)
    super(server)
    @xmppproxy, @user, @pass, = options
  end

  def auth(req, res)
    unless @user.nil? and @pass.nil?
      WEBrick::HTTPAuth.basic_auth(req, res, AUTHREALM) do |user, pass|
        user == @user && pass == @pass
      end
    end
  end

  def do_GET(req, res)
    auth(req, res)
    if req.query["t"].nil? || req.query["t"].empty?
      show_list(req, res)
      return
    end
    jid = req.query["t"]
    msg = ''
    if has_lcs?(req)
      # location
      mvalue = lcs_gmap_url(req)
    end
    show_mesg(req, res, jid, mvalue)
  end

  def do_POST(req, res)
    auth(req, res)
    if req.query["t"].nil? || req.query["t"].empty?
      raise WEBrick::HTTPStatus::BadRequest, "no t param"
    end
    jid = req.query["t"]
    if ! req.query["m"].nil? && ! req.query["m"].empty?
      @xmppproxy.send(jid, 
                      Kconv.kconv(req.query["m"], Kconv::UTF8, Kconv::SJIS))
      sleep(1)
    end
    show_mesg(req, res, jid, '')
  end

  def show_list(req, res)
    res.status = 200
    res.content_type = "text/html; charset=Shift_JIS"
    list = ""
    @xmppproxy.list_targets().each_with_index do |jid, index|
      @logger.info("#{index} #{jid}")
      jid_escaped = WEBrick::HTMLUtils::escape(jid.to_s)
      if index < 10
        list += <<LINK
#{index} <a accesskey="#{index}" href="#{req.request_uri.path}?t=#{jid_escaped}">#{jid_escaped}</a><br>
LINK
      else
        list += <<LINK
<a href="#{req.request_uri.path}?t=#{jid_escaped}">#{jid_escaped}</a><br>
LINK
      end
    end
    res.body = <<HTML
<html><head><meta http-equiv="Cache-Control" content="max-age=0" /><title>Monogiki</title></head><body>
#{Kconv.kconv(list, Kconv::SJIS, Kconv::UTF8)}
</body></html>
HTML
  end

  def show_mesg(req, res, jid, mvalue)
    res.status = 200
    res.content_type = "text/html; charset=Shift_JIS"
    msg = WEBrick::HTMLUtils::escape(@xmppproxy.get_messages(jid).join("\n"))
    jid_escaped = WEBrick::HTMLUtils::escape(jid.to_s)
    # TODO(ukai): show lcs only if the UA supports GPS feature.
    res.body = <<HTML
<html><head><meta http-equiv="Cache-Control" content="max-age=0" /><title>Monogiki: #{jid}</title></head><body>
<form action="#{req.request_uri.path}" method="POST"><input type="hidden" name="t" value="#{jid_escaped}"><input type="text" name="m" size="10" value="#{mvalue}"><input type="submit" accesskey="1" value="OK[1]"><a accesskey="8" href="#{req.request_uri.path}">list[8]</a><a accesskey="*" href="#{req.request_uri.path}?t=#{jid_escaped}" lcs>loc[*]</a></form><pre>
#{Kconv.kconv(msg, Kconv::SJIS, Kconv::UTF8)}
</pre>
<p>#{jid_escaped}</p></body></html>
HTML
  end

  # location (DoCoMo specific)
  # http://www.nttdocomo.co.jp/service/imode/make/content/gps/
  def has_lcs?(req)
    return ! req.query["lat"].nil? && ! req.query["lat"].empty? &&
      ! req.query["lon"].nil? && ! req.query["lon"].empty?
  end
  def lcs_gmap_url(req)
    lat = WEBrick::HTTPUtils.escape(lcs_value(req.query["lat"]))
    lon = WEBrick::HTTPUtils.escape(lcs_value(req.query["lon"]))
    # Google Maps URL.
    # http://maps.google.co.jp/support/bin/answer.py?answer=18539&topic=10780
    # use + (space) instead of ,(comma), because twitter tinyurl confuses.
    return "http://maps.google.co.jp/maps?q=#{lat}+#{lon} "
  end
  def lcs_value(s)
    lcs = s.split('.')
    f = lcs[1].to_f/60.0 + lcs[2].to_f/(60.0*60.0) +
      lcs[3].to_f/(60.0*60.0*100.0)
    return "#{lcs[0]}.#{f.to_s.sub(/^0\./, "")[0,6]}"
  end

end # XmppServlet

if __FILE__ == $0
  logger = WEBrick::Log.new
  logger.info("Momogiki #{Monogiki::VERSION}")
  CONFIGFILE = ENV['MONOGIKIRC'] || "~/.monogikirc"

  logger.info("Loading config: #{CONFIGFILE}")
  load CONFIGFILE

  xmpp = XmppProxy.new({ :MyJID => MYJID,
                         :MyPassword => MYJIDPASSWD,
                         :TargetJIDList => TARGETJIDLIST,
                         :Logger => logger})
  server = WEBrick::HTTPServer.new({ :BindAddress => HTTPADDR,
                                     :Port => HTTPPORT,
                                     :Logger => logger})
  ['INT', 'TERM'].each do |signal|
    Signal.trap(signal) { server.shutdown }
  end
  server.mount('/', XmppServlet, xmpp, HTTPUSER, HTTPPASSWD)
  server.start
end
